/* 
	* Created by Varun Tyagi
	*
	* This is the first tutorial in which I have tried to do a collision detection, using 
	* per pixel collision detection method.
	*
	* Using rectangular detection method for every thing would result in loss and inaccurate result.
*/

/* Include the header files. */
#include "SDL.h"
#include "SDL_image.h"
#include <string>
#include <vector>

/* The screen attributes. */
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
const int SCREEN_BPP = 32;

/* Frames per second in the game. */
const int FRAMES_PER_SECOND = 20;

/* Attributes of the square. */
const int DOT_HEIGHT = 20;
const int DOT_WIDTH = 20;

/* The surface. */
SDL_Surface *pDot = nullptr;
SDL_Surface *pScreen = nullptr;

/* Event structure. */
SDL_Event event;

/* DOT class. */
class Dot
{
private:

	/* Offset of the dot. */
	int m_iPosX, m_iPosY;

	/* The collision box of the dot. */
	std::vector<SDL_Rect> box;

	/* Velocity of the box. */
	int m_iVelX, m_iVelY;

	/* Move the collision box relative to the offset of the dot. */
	void shift_box( void );

public:

	/* Initialize. */
	Dot( int iX, int iY );

	/* Input handler for the entity. */
	void handle_input( void );

	/* Move the entity. */
	void move( std::vector<SDL_Rect> &rects );

	/* Show the entity on the screen. */
	void show( void );

	/* Get the collision boxes. */
	std::vector<SDL_Rect> &get_rects( void );
};

/* TIMER class. */
class Timer
{
private:

	/* The timer when the timer was started. */
	int m_iStartTime;

	/* The tick stored when the timer was paused. */
	int m_iPauseTime;

	/* Timer status. */
	bool m_bStarted;
	bool m_bPaused;

public:

	/* Initialize. */
	Timer();

	/* Timer action functions. */
	void start( void );
	void stop( void );
	void pause( void );
	void unpause( void );

	/* Get the current tick. */
	int get_ticks( void );

	/* Check the status of the timer. */
	bool has_started( void );
	bool is_paused( void );
};

/* Image loader. */
SDL_Surface* load_image( std::string filename )
{
	/* Loaded image. */
	SDL_Surface *pLoadedImage;

	/* Optimized image. */
	SDL_Surface *pOptimizedImage;

	/* Load the image. */
	pLoadedImage = IMG_Load( filename.c_str() );

	/* Check for the loaded image. */
	if( nullptr != pLoadedImage )
	{
		/* Optimize the image. */
		pOptimizedImage = SDL_DisplayFormat( pLoadedImage );

		/* If the image has been optimized fine, color it to the background. */
		if( nullptr != pOptimizedImage )
		{
			/* Color key surface. */
			SDL_SetColorKey( pOptimizedImage, SDL_SRCCOLORKEY, SDL_MapRGB( pOptimizedImage->format, 0, 0xFF, 0xFF ) );
		}
	}

	/* Return the optimized image. */
	return pOptimizedImage;
}

/* Surface applier. */
void apply_surface( int x, int y, SDL_Surface *pSource, SDL_Surface *pDestination, SDL_Rect *pRect = nullptr )
{
	/* Offset of the image. */
	SDL_Rect offset;

	/* Get the image offsets. */
	offset.x = x;
	offset.y = y;

	/* Blit the image. */
	SDL_BlitSurface( pSource, pRect, pDestination, &offset );
}

/* Collision checker. */
bool check_collision( std::vector<SDL_Rect> &A, std::vector<SDL_Rect> &B )
{
	/* Sides of a rectangle. */
	int iLeftA, iLeftB;
	int iRightA, iRightB;
	int iTopA, iTopB;
	int iBottomA, iBottomB;

	/* Go through the A boxes. */
	for( int ABox = 0; ABox < A.size(); ++ABox )
	{
		/* Calculate the side of rect A. */
		iLeftA		= A[ ABox ].x;
		iRightA		= A[ ABox ].x + A[ ABox ].w;
		iTopA		= A[ ABox ].y;
		iBottomA	= A[ ABox ].y + A[ ABox ].h;

		/* Go through the B boxes. */
		for( int BBox = 0; BBox < B.size(); ++BBox )
		{
			/* Calculate the side of Rect B. */
			iLeftB		= B[ BBox ].x;
			iRightB		= B[ BBox ].x + B[ BBox ].w;
			iTopB		= B[ BBox ].y;
			iBottomB	= B[ BBox ].y + B[ BBox ].h;

			/* Check for collision. If no sides from A is outside of B. */
			if( ( ( iBottomA <= iTopB ) || ( iTopA >= iBottomB ) || ( iRightA <= iLeftB ) || ( iLeftA >= iRightB ) ) == false )
			{
				/* A collision is detected. */
				return true;
			}
		}
	}

	/* No collision. */
	return false;
}

/* Initialize SDL. */
bool init_SDL( void )
{
	/* Initialize SDL .*/
	if( -1 == SDL_Init( SDL_INIT_EVERYTHING ) )
	{
		return false;
	}

	/* Set up the window screen. */
	pScreen = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE );

	/* If the screen is not loaded properly. */
	if( nullptr == pScreen )
	{
		return false;
	}

	/* Set the window caption. */
	SDL_WM_SetCaption( "Move the Dot.", nullptr );

	/* return. */
	return true;
}

/* File Loader. */
bool load_files( void )
{
	/* Load the image. */
	pDot = load_image( "dot.bmp" );

	/* Check the loaded image */
	if( nullptr == pDot )
	{
		return false;
	}

	/* return. */
	return true;
}

/* Clean up. */
void clean_SDL( void )
{
	/* Free the surface. */
	SDL_FreeSurface( pDot );

	/* QUIT. */
	SDL_Quit();
}

/************ SQUARE *************/
Dot::Dot( int iX, int iY )
	: m_iPosX( iX )
	, m_iPosY( iY )
{
	/* Initialize the velocity. */
	m_iVelX = m_iVelY = 0;

	/* Create the necessary SDL_Rects. */
	box.resize( 11 );

	/* Initialize the collision boxes width and height. */
	box[ 0 ].w = 6;
	box[ 0 ].h = 1;

	box[ 1 ].w = 10;
	box[ 1 ].h = 1;

	box[ 2 ].w = 14;
	box[ 2 ].h = 1;

	box[ 3 ].w = 16;
	box[ 3 ].h = 2;

	box[ 4 ].w = 18;
	box[ 4 ].h = 2;

	box[ 5 ].w = 20;
	box[ 5 ].h = 6;

	box[ 6 ].w = 18;
	box[ 6 ].h = 2;

	box[ 7 ].w = 16;
	box[ 7 ].h = 2;

	box[ 8 ].w = 14;
	box[ 8 ].h = 1;

	box[ 9 ].w = 10;
	box[ 9 ].h = 1;

	box[ 10 ].w = 6;
	box[ 10 ].h = 1;
	
	/* Move the collision boxes to their proper spot. */
	shift_box();
}

void Dot::shift_box( void )
{
	/* The row offset. */
	int r = 0;

	/* Go through the dots collision box. */
	for( int set = 0; set < box.size(); ++set )
	{
		/* Center the collision box. */
		box[ set ].x = m_iPosX + ( DOT_WIDTH - box[ set ].w ) / 2;

		/* Set the collision box at its row offset. */
		box[ set ].y = m_iPosY + r;

		/* Move the row offset down the height of the collision box. */
		r += box[ set ].h;
	}
}

void Dot::handle_input( void )
{
	/* If a key is pressed down. */
	if( SDL_KEYDOWN == event.type )
	{
		/* Adjust the velocity. */
		switch( event.key.keysym.sym )
		{
		case SDLK_KP8:
			{
				m_iVelY -= 1;
				break;
			}
		case SDLK_KP2:
			{
				m_iVelY += 1;
				break;
			}
		case SDLK_KP4:
			{
				m_iVelX -= 1;
				break;
			}
		case SDLK_KP6:
			{
				m_iVelX += 1;
				break;
			}
		}
	}

	/* If a key is released. */
	if( SDL_KEYUP == event.type )
	{
		switch( event.key.keysym.sym )
		{
		case SDLK_KP8:
			{
				m_iVelY += 1;
				break;
			}
		case SDLK_KP2:
			{
				m_iVelY -= 1;
				break;
			}
		case SDLK_KP4:
			{
				m_iVelX += 1;
				break;
			}
		case SDLK_KP6:
			{
				m_iVelX -= 1;
				break;
			}
		}
	}
}

void Dot::move( std::vector<SDL_Rect> &rects )
{
	/* Move the box left or right. */
	m_iPosX += m_iVelX;

	/* Shift the collision boxes. */
	shift_box();

	/* If the box went too far, or if the box hit the wall. */
	if( ( m_iPosX	 < 0 ) || ( m_iPosX + DOT_WIDTH > SCREEN_WIDTH ) || ( check_collision( box, rects ) ) )
	{
		/* Move back. */
		m_iPosX -= m_iVelX;
		shift_box();
	}

	/* Move the square up and down. */
	m_iPosY += m_iVelY;

	/* If the box went too far, or if the box hit the wall. */
	if( ( m_iPosY < 0 ) || ( m_iPosY + DOT_HEIGHT > SCREEN_HEIGHT ) || check_collision( box, rects ) )
	{
		/* Move back. */
		m_iPosY -= m_iVelY;
		shift_box();
	}
}

void Dot::show( void )
{
	/* Apply the image. */
	apply_surface( m_iPosX, m_iPosY, pDot, pScreen );
}

std::vector<SDL_Rect> &Dot::get_rects( void )
{
	/* Retrieve the collision box. */
	return box;
}

/************ SQUARE *************/
Timer::Timer()
{
	/* Initialize. */
	m_iStartTime = 0;
	m_iPauseTime = 0;
	m_bStarted = false;
	m_bPaused = false;
}

void Timer::start( void )
{
	m_bStarted = true;

	m_bPaused = false;

	/* Start the timer. */
	m_iStartTime = SDL_GetTicks();
}

void Timer::stop( void )
{
	m_bStarted = false;

	/* Unpause. */
	m_bPaused = false;
}

void Timer::pause( void )
{
	/* If the timer has started and is not paused. */
	if( ( true == m_bStarted ) && ( false == m_bPaused ) )
	{
		/* Pause the timer. */
		m_bPaused = true;

		/* Calculate the pause ticks. */
		m_iPauseTime = SDL_GetTicks() - m_iStartTime;
	}
}

void Timer::unpause( void )
{
	/* Check to see if the timer is paused. */
	if( true == m_bPaused )
	{
		/* Unpause the timer. */
		m_bPaused = false;

		/* Reset the starting tick. */
		m_iStartTime = SDL_GetTicks() - m_iPauseTime;

		/* Reset the pause time. */
		m_iPauseTime = 0;
	}
}

int Timer::get_ticks( void )
{
	/* If the timer is running. */
	if( true == m_bStarted  )
	{
		/* If the timer is paused. */
		if( true == m_bPaused )
		{
			/* return the pause tick. */
			return m_iPauseTime;
		}
		else
		{
			return SDL_GetTicks() - m_iStartTime;
		}
	}

	/* Else return 0. */
	return 0;
}

bool Timer::has_started( void )
{
	return m_bStarted;
}

bool Timer::is_paused( void )
{
	return m_bPaused;
}



/* MAIN FUNCTION. */
int main( int argc, char** argv )
{
	/* QUIT flag. */
	bool bQuit = false;

	/* The entity. */
	Dot myDot( 0, 0 );

	Dot otherDot( 20, 20 );

	/* Frame Counter. */
	Timer fps;

	/* Initialize. */
	if( false == init_SDL() )
	{
		return 1;
	}

	/* Load files. */
	if( false == load_files() )
	{
		return 1;
	}


	/* While the user hasn't quit. */
	while( false == bQuit )
	{
		/* Start the frames. */
		fps.start();

		/* While there is an event to handle. */
		while( SDL_PollEvent( &event ) )
		{
			/* Handle the object event. */
			myDot.handle_input();

			/* If the user has Xed out the window. */
			if( SDL_QUIT == event.type )
			{
				/* QUIT. */
				bQuit = true;
			}
		}

		/* Move the entity. */
		myDot.move( otherDot.get_rects() );

		/* Fill the screen with white color. */
		SDL_FillRect( pScreen, &pScreen->clip_rect, SDL_MapRGB( pScreen->format, 0xFF, 0xFF, 0xFF ) );

		/* Show the other dot on the screen. */
		otherDot.show();
		myDot.show();

		/* Update the screen. */
		if( -1 == SDL_Flip( pScreen ) )
		{
			return 1;
		}

		/* Cap the FPS. */
		if( fps.get_ticks() < 1000 / FRAMES_PER_SECOND )
		{
			SDL_Delay( ( 1000/ FRAMES_PER_SECOND ) - fps.get_ticks() );
		}
	}

	/* Clean up. */
	clean_SDL();

	return 0;
}